Skip to content

Integrate DSA training into Megatron-LM - #1

Draft
hawkoli1987 wants to merge 1 commit into
mainfrom
cursor/integrate-dsa-training-into-megatron-lm-7eb2
Draft

Integrate DSA training into Megatron-LM#1
hawkoli1987 wants to merge 1 commit into
mainfrom
cursor/integrate-dsa-training-into-megatron-lm-7eb2

Conversation

@hawkoli1987

Copy link
Copy Markdown
Owner

What does this PR do ?

This PR introduces DeepSeek Sparse Attention (DSA) training capabilities to Megatron-LM, allowing models to learn and apply sparse attention masks during training.

⚠️ For major changes (either in lines of code or in its impact), please make sure to first share discuss a design-doc with the team.

Contribution process

flowchart LR
    A[Pre-checks] --> B[PR Tests]
    subgraph Code Review/Approval
        C1[Expert Review] --> C2[Final Review]
    end
    B --> C1
    C2 --> D[Merge]
Loading

Pre-checks

  • I want this PR in a versioned release and have added the appropriate Milestone (e.g., Core 0.8)
  • I have added relevant unit tests
  • I have added relevant functional tests
  • I have added proper typing to my code Typing guidelines
  • I have added relevant documentation
  • I have run the autoformatter.sh on my PR

Code review

The following process is enforced via the CODEOWNERS file for changes into megatron/core. For changes outside of megatron/core, it is up to the PR author whether or not to tag the Final Reviewer team.

For MRs into `main` branch

(Step 1): Add PR label Expert Review

(Step 2): Collect the expert reviewers reviews

  1. Attach the Expert Review label when your PR is ready for review.
  2. GitHub auto-assigns expert reviewers based on your changes. They will get notified and pick up your PR soon.

⚠️ Only proceed to the next step once all reviewers have approved, merge-conflict are resolved and the CI is passing.
Final Review might get declined if these requirements are not fulfilled.

(Step 3): Final Review

  1. Add Final Review label
  2. GitHub auto-assigns final reviewers based on your changes. They will get notified and pick up your PR soon.

(Optional Step 4): Cherry-pick into release branch

If this PR also needs to be merged into core_r* release branches, after this PR has been merged, select Cherry-pick to open a new PR into the release branch.

For MRs into `dev` branch The proposed review process for `dev` branch is under active discussion.

MRs are mergable after one approval by either eharper@nvidia.com or zijiey@nvidia.com.

Merging your PR

Any member of core-adlr and core-nemo will be able to merge your PR.


Open in Cursor Open in Web

Integrates DSA into MultiLatentAttention and TransformerConfig.

Co-authored-by: yuli <yuli@aisingapore.org>
@cursor

cursor Bot commented Nov 12, 2025

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Comment on lines +20 to +35
def _hadamard_transform(x: Tensor) -> Tensor:
"""Apply an in-place Hadamard transform along the last dimension."""
hidden = x.size(-1)
if hidden & (hidden - 1) != 0:
raise ValueError(
f"DSA requires index head dimension to be a power of two, but got {hidden}."
)
stride = 1
out = x
while stride < hidden:
out = out.reshape(*out.shape[:-1], -1, 2, stride)
first = out[..., 0, :]
second = out[..., 1, :]
out = torch.cat((first + second, first - second), dim=-1)
stride <<= 1
return out / math.sqrt(hidden)

@nagarajankarthik nagarajankarthik Nov 13, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this implementation is correct. I tested it using the following Python script:

import torch
import torch.nn.functional as F
import scipy


def reference_hadamard(x: torch.Tensor, scale: float = 1.0):
    """
    See https://github.com/Dao-AILab/fast-hadamard-transform
    """
    dim = x.size(-1)
    scale = dim**-0.5
    return F.linear(x, torch.Tensor(scipy.linalg.hadamard(dim))) * scale


def cursor_hadamard(x: torch.Tensor, scale: float = 1.0):
    """
    Hadamard transform function written by Cursor.
    """
    hidden = x.size(-1)
    if hidden & (hidden - 1) != 0:
        raise ValueError(
            f"DSA requires index head dimension to be a power of two, but got {hidden}."
        )
    stride = 1
    out = x
    while stride < hidden:
        out = out.reshape(*out.shape[:-1], -1, 2, stride)
        first = out[..., 0, :]
        second = out[..., 1, :]
        out = torch.cat((first + second, first - second), dim=-1)
        stride <<= 1
    return out / math.sqrt(hidden)


def main():
    x = torch.randn(2, 2, 4)
    y1 = reference_hadamard(x)
    print("Reference result for hadamard transform")
    print(y1)
    y2 = cursor_hadamard(x)
    print("Cursor result for hadamard transform")
    print(y2)

    print("Hello from dsa-cursor!")


if __name__ == "__main__":
    main()

The output is the following:

Reference result for hadamard transform
tensor([[[ 0.2654,  1.8008, -0.8284,  0.7358],
         [ 2.0578,  0.3078, -0.5755, -0.1308]],

        [[ 0.3302, -0.1754,  0.2045,  0.0398],
         [ 0.3369, -2.5096, -0.6018, -0.5389]]])
Traceback (most recent call last):
  File "/data/projects/71001002/nkarthik/dsa_cursor/test.py", line 48, in <module>
    main()
  File "/data/projects/71001002/nkarthik/dsa_cursor/test.py", line 40, in main
    y2 = cursor_hadamard(x)
         ^^^^^^^^^^^^^^^^^^
  File "/data/projects/71001002/nkarthik/dsa_cursor/test.py", line 27, in cursor_hadamard
    out = out.reshape(*out.shape[:-1], -1, 2, stride)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: shape '[2, 2, 2, -1, 2, 2]' is invalid for input of size 16

The reference implementation taken from here works but cursor's does not. It could be that cursor's version is not designed to handle the specific input that I used.
Perhaps we can directly use the hadamard transform implementation provided here instead of trying to write it ourselves?


weights = self.weight_proj(hidden_states) * (self.index_heads ** -0.5)

raw = torch.einsum("bqhd,bkd->bqhk", q_latent, k_latent)

@nagarajankarthik nagarajankarthik Nov 13, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the DeepSeek code, the q_latent and k_latent are first scaled to fp8 before calculating the index scores. Is this being done somewhere else in this implementation?

I am also wondering whether it is ok to proceed with implementation of the Lightning Indexer in bf16 in the first instance and work on the low precision part later?

batch = hidden_states.size(1)
dsa_hidden = hidden_states.permute(1, 0, 2).contiguous()
if self.config.q_lora_rank is not None:
qr_states = q_compressed.permute(1, 0, 2).contiguous()

@nagarajankarthik nagarajankarthik Nov 13, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The q_compressed does not seem to be defined anywhere in this function. It is actually computed in the get_query_key_value_tensors function later in this script. But that function does not return q_compressed. This part of the code might need to be restructured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants